package top.whysu.gps.service.system.impl;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Validator;
import com.alibaba.fastjson.JSON;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import top.whysu.gps.constant.CodeConstant;
import top.whysu.gps.dao.relative.RelativeAccountRoleDao;
import top.whysu.gps.dao.relative.RelativeRoleCategoryDao;
import top.whysu.gps.dao.system.AccountDao;
import top.whysu.gps.dao.system.CategoryDao;
import top.whysu.gps.dao.system.RoleDao;
import top.whysu.gps.dto.page.PagenationDataDTO;
import top.whysu.gps.dto.system.PermissionDTO;
import top.whysu.gps.dto.system.RoleDTO;
import top.whysu.gps.dto.system.RoleQueryDTO;
import top.whysu.gps.exception.BusinessException;
import top.whysu.gps.po.relative.RelativeAccountRolePO;
import top.whysu.gps.po.relative.RelativeRoleCategoryPO;
import top.whysu.gps.po.system.AccountPO;
import top.whysu.gps.po.system.CategoryPO;
import top.whysu.gps.po.system.RolePO;
import top.whysu.gps.service.system.AccountService;
import top.whysu.gps.service.system.RoleService;
import top.whysu.gps.utils.ConverterUtils;
import top.whysu.gps.utils.PageUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;

/**
 * 系统角色业务实现类
 */
@Service("roleServiceImpl")
public class RoleServiceImpl implements RoleService {

    private static final Logger LOGGER = LoggerFactory.getLogger(RoleServiceImpl.class);
    @Resource
    private RoleDao roleDao;
    @Resource
    private RelativeRoleCategoryDao relativeRoleCategoryDao;
    @Resource
    private RelativeAccountRoleDao relativeAccountRoleDao;
    @Resource
    private CategoryDao categoryDao;
    @Resource
    private AccountService accountService;
    @Resource
    private AccountDao accountDao;

    /**
     * 新增角色
     *
     * @param roleDTO 角色传输对象
     */
    @Override
    public void insertRole(RoleDTO roleDTO) {
        // 基础校验
        if (Validator.isEmpty(roleDTO.getRoleName())) {
            throw new BusinessException(CodeConstant.Http.INFO_ERROR, "角色名称不允许空值!");
        }
        if (Validator.isEmpty(roleDTO.getPermissions())) {
            throw new BusinessException(CodeConstant.Http.INFO_ERROR, "平台权限需打勾,不允许空!");
        }
        // 判断角色名称是否已经存在
        RolePO tempRolePO = roleDao.selectByRoleName(roleDTO.getRoleName());
        if (tempRolePO != null && StringUtils.isNotBlank(tempRolePO.getRoleName())) {
            throw new BusinessException(CodeConstant.Http.INFO_ERROR, "该角色名称已经存在!");
        }
        // 校验本地平台权限数量是否正确
        checkPermLocalCount(roleDTO.getPermissions());

        // 生成数据
        RolePO rolePO = ConverterUtils.dto2po(roleDTO, RolePO.class);
        // 生成角色表
        roleDao.insert(rolePO);
        // 更新Sign的值
        rolePO.setRoleSign(Base64.encode(rolePO.getId().toString()));
        roleDao.update(rolePO);

        // 添加默认数据
        List<PermissionDTO> distinctList = this.wholeAddDefault(roleDTO.getPermissions());
        // 生成角色-菜单关联表
        for (PermissionDTO perm : distinctList) {
            if (StringUtils.isNotBlank(perm.getId())) {
                RelativeRoleCategoryPO relativeRoleCategoryPO = new RelativeRoleCategoryPO();
                relativeRoleCategoryPO.setCategoryId(new BigDecimal(perm.getId()));
                relativeRoleCategoryPO.setRoleId(rolePO.getId());
                relativeRoleCategoryDao.insert(relativeRoleCategoryPO);
            }
        }
    }

    /**
     * 添加默认数据并去重
     */
    private List<PermissionDTO> wholeAddDefault(List<PermissionDTO> allPermissions) {
        // 默认添加本地全局菜单
        PermissionDTO globalPerm = new PermissionDTO();
        globalPerm.setId("1");
        allPermissions.add(globalPerm);
        // 默认添加勾选的本地菜单数据的父级权限
        Set<String> parentIdSet = new HashSet<>();
        for (PermissionDTO perm : allPermissions) {
            this.selectParentCategory(perm, parentIdSet);
        }
        for (String parentId : parentIdSet) {
            PermissionDTO parentPerm = new PermissionDTO();
            parentPerm.setId(parentId);
            allPermissions.add(parentPerm);
        }
        // 去重
        List<PermissionDTO> distinctList = new ArrayList<>();
        for (PermissionDTO perm : allPermissions) {
            boolean flag = distinctList.stream().anyMatch(it -> it.getId().equals(perm.getId()));
            if (!flag) {
                distinctList.add(perm);
            }
        }
        return distinctList;
    }

    /**
     * 向上递归获取父级数据
     */
    private void selectParentCategory(PermissionDTO perm, Set<String> idSet) {
        CategoryPO childPerm = categoryDao.selectById(new BigDecimal(perm.getId()));
        CategoryPO parent = categoryDao.selectById(childPerm.getParentid());
        if (null != parent) {
            idSet.add(parent.getId().toString());
            PermissionDTO parentPerm = new PermissionDTO();
            parentPerm.setId(parent.getId().toString());
            this.selectParentCategory(parentPerm, idSet);
        }
    }

    /**
     * 查看角色
     *
     * @param id 主键
     * @return 角色信息
     */
    @Override
    public RoleDTO selectRole(BigDecimal id) {
        RolePO rolePO = roleDao.selectById(id);
        RoleDTO roleDTO = ConverterUtils.po2dto(rolePO, RoleDTO.class);
        // 所有平台的权限
        List<PermissionDTO> allPermissions = new ArrayList<>();
        // 本地权限-通过数据库查询
        Map<String, Object> map = new HashMap<>(2);
        map.put("roleId", roleDTO.getId());
        map.put("description", "叶子节点");
        List<RelativeRoleCategoryPO> relativeRoleCategoryPOs = relativeRoleCategoryDao.selectByRoleAndType(map);
        for (RelativeRoleCategoryPO relativeRoleCategoryPO : relativeRoleCategoryPOs) {
            PermissionDTO permLocal = new PermissionDTO();
            permLocal.setId(relativeRoleCategoryPO.getCategoryId().toString());
            allPermissions.add(permLocal);
        }
        roleDTO.setPermissions(allPermissions);
        return roleDTO;
    }

    /**
     * 修改角色
     *
     * @param roleDTO
     */
    @Override
    public void updateRole(RoleDTO roleDTO) {
        // 基础校验
        if (Validator.isEmpty(roleDTO.getRoleName())) {
            throw new BusinessException(CodeConstant.Http.INFO_ERROR, "角色名称不允许空值!");
        }
        if (Validator.isEmpty(roleDTO.getPermissions())) {
            throw new BusinessException(CodeConstant.Http.INFO_ERROR, "平台权限需打勾,不允许空!");
        }
        // 判断角色名称是否已经存在(还要判断id和当前id是否一样)
        RolePO tempRolePO = roleDao.selectByRoleName(roleDTO.getRoleName());
        if (null != tempRolePO && !tempRolePO.getId().equals(roleDTO.getId())) {
            throw new BusinessException(CodeConstant.Http.INFO_ERROR, "该角色名称已经存在!");
        }
        // 校验本地平台权限数量是否正确
        checkPermLocalCount(roleDTO.getPermissions());
        // 在update之前，获取roleId所关联的账号信息集合
        List<AccountPO> oldAccountList = getAccountListByRoleId(roleDTO.getId());
        // 更新角色表
        RolePO rolePO = ConverterUtils.dto2po(roleDTO, RolePO.class);
        roleDao.update(rolePO);

        // [权限有变动时]重新生成角色-菜单关联表
        // 1-删除角色-菜单关联表
        relativeRoleCategoryDao.deleteByRoleId(roleDTO.getId());
        // 添加默认数据
        List<PermissionDTO> distinctList = this.wholeAddDefault(roleDTO.getPermissions());
        for (PermissionDTO perm : distinctList) {
            if (StringUtils.isNotBlank(perm.getId())) {
                RelativeRoleCategoryPO relativeRoleCategoryPO = new RelativeRoleCategoryPO();
                relativeRoleCategoryPO.setRoleId(roleDTO.getId());
                relativeRoleCategoryPO.setCategoryId(new BigDecimal(perm.getId()));
                relativeRoleCategoryPO.setCreateTime(new Date());
                relativeRoleCategoryPO.setUpdateTime(new Date());
                relativeRoleCategoryDao.insert(relativeRoleCategoryPO);
            }
        }
    }


    @Override
    public PagenationDataDTO<RolePO> selectPageRole(RoleQueryDTO queryDTO) {
        // 获取集合
        LinkedHashMap map = JSON.parseObject(JSON.toJSONString(queryDTO), LinkedHashMap.class);
        // limit的起始值
        map.put("rowIndex", PageUtils.getRowIndex(queryDTO.getPageNum(), queryDTO.getPageSize()));
        List<RolePO> roleList = roleDao.selectList(map);
        // 获取总数
        long totalSize = roleDao.countList(map);
        if (CollectionUtils.isNotEmpty(roleList) && totalSize > 0) {
            return new PagenationDataDTO<>(totalSize, queryDTO.getPageNum(), queryDTO.getPageSize(), roleList);
        } else {
            return PageUtils.toPagedResult(null);
        }
    }

    /**
     * 根据用户ID，查询相应用户的角色
     *
     * @param userId
     * @return
     */
    @Override
    public List<RolePO> selectRolesByUserId(BigDecimal userId) {
        return roleDao.selectRolesByUserId(userId);
    }

    /**
     * 根据角色标志查询角色信息
     *
     * @param roleSign
     * @return
     */
    @Override
    public RolePO selectByRoleSign(String roleSign) {
        return roleDao.selectByRoleSign(roleSign);
    }

    /**
     * 删除角色
     *
     * @param id 主键
     */
    @Override
    public void deleteRole(BigDecimal id) {
        // 存在账户时不可删除
        List<RelativeAccountRolePO> relList = relativeAccountRoleDao.selectByRoleId(id);
        if (!relList.isEmpty()) {
            throw new BusinessException(CodeConstant.Http.EXECUTE_FAIL, "该角色下有账号存在，不允许删除！");
        }
        // 删除角色表
        roleDao.delete(id);
        // 删除角色-菜单关联表
        relativeRoleCategoryDao.deleteByRoleId(id);
    }

    /**
     * 角色下拉框查询
     *
     * @return
     */
    @Override
    public List<Map<String, Object>> selectListRole() {
        // 1:查询角色信息
        List<RolePO> rolePOList = roleDao.selectList(null);
        List<Map<String, Object>> resultList = new ArrayList<>();
        for (RolePO rolePO : rolePOList) {
            Map<String, Object> resultMap = new HashMap<>(16);
            resultMap.put("roleSign", rolePO.getRoleSign());
            resultMap.put("roleName", rolePO.getRoleName());
            resultList.add(resultMap);
        }
        // 2:查询当前账号角色信息
        Map<String, Object> paramMap = new HashMap<>();
        Subject subject = SecurityUtils.getSubject();
        String userName = (String) subject.getPrincipal();
        AccountPO accountPo = accountService.selectByUsername(userName);
        List<RolePO> rolePOS = roleDao.selectRolesByUserId(accountPo.getId());
        if (CollectionUtil.isNotEmpty(rolePOS)) {
            paramMap.put("currentAccountRole", rolePOS.get(0).getRoleName());
            paramMap.put("currentAccountValue", rolePOS.get(0).getRoleSign());
            resultList.add(paramMap);
        }
        return resultList;
    }

    /**
     * 校验平安社区本地平台权限数量
     *
     * @param allPermissionList 权限树集合
     * @return 平安社区本地平台权限数量
     */
    private int checkPermLocalCount(List<PermissionDTO> allPermissionList) {
        int permLocalCount = 0;
        if (org.apache.commons.collections.CollectionUtils.isNotEmpty(allPermissionList)) {
            for (PermissionDTO perm : allPermissionList) {
                permLocalCount++;
            }
        }
        if (permLocalCount <= 0) {
            throw new BusinessException(CodeConstant.Http.INFO_ERROR, "平安社区后台权限需打勾,不允许空!");
        }
        return permLocalCount;
    }

    /**
     * 根据roleId获取关联的所有账号信息
     *
     * @param roleId e_system_role角色表的id
     * @return 账号信息
     */
    private List<AccountPO> getAccountListByRoleId(BigDecimal roleId) {
        List<AccountPO> accountList = new ArrayList<>();
        if (roleId == null) {
            LOGGER.error("将该角色的所有已登录的账号都强制下线失败，roleId为空");
            return accountList;
        }
        // 查询roleId关联的所有账号
        List<RelativeAccountRolePO> accountRoleList = relativeAccountRoleDao.selectByRoleId(roleId);
        if (CollectionUtils.isNotEmpty(accountRoleList)) {
            accountRoleList.forEach(it -> {
                if (it.getAccountId() != null) {
                    // 根据accountId查询账号信息
                    AccountPO accountPO = accountDao.selectById(it.getAccountId());
                    if (accountPO != null) {
                        accountList.add(accountPO);
                    }
                }
            });
        }
        return accountList;
    }

}
